﻿using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.DependencyInjection;
using EFCore.BulkExtensions;

namespace Joy.Erp.BasicArchives.Core
{
    public class MultiArchiveRepository<TDbContext, TEntity, TChild, TKey> : EfCoreRepository<TDbContext, TEntity, TKey>,
        IMultiArchiveRepository<TEntity, TChild, TKey>
        where TDbContext : IEfCoreDbContext
        where TEntity : class, IMultiArchiveAggregateRoot<TChild, TKey>
        where TChild : class, IArchiveChildAggregateRoot<TKey>
    {

        private readonly Lazy<AbpEntityOptions<TChild>> _childOptionsLazy;
        protected virtual AbpEntityOptions<TChild> AbpChildOptions => _childOptionsLazy.Value;

        public MultiArchiveRepository(IDbContextProvider<TDbContext> dbContextProvider) : base(dbContextProvider)
        {
            _childOptionsLazy = new Lazy<AbpEntityOptions<TChild>>(
                () => ServiceProvider
                          .GetRequiredService<IOptions<AbpEntityOptions>>()
                          .Value
                          .GetOrNull<TChild>() ?? AbpEntityOptions<TChild>.Empty
            );
        }

        public override async Task<IQueryable<TEntity>> GetQueryableAsync()
        {
            return ApplyDataFilters((await GetDbSetAsync()).AsQueryable());
        }

        /// <summary>
        /// 批量插入实体（高性能，使用 EFCore.BulkExtensions）
        /// </summary>
        public virtual async Task BulkInsertAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            var entityArray = entities.ToArray();
            if (entityArray.Length == 0) return;

            var dbContext = await GetDbContextAsync();
            cancellationToken = GetCancellationToken(cancellationToken);

            // 使用 EFCore.BulkExtensions 的 BulkInsertAsync 进行高性能批量插入
            // TDbContext 约束为 IEfCoreDbContext，而所有 ABP 的 DbContext 都继承自 DbContext
            // 通过 as 转换确保类型安全
            var efDbContext = dbContext as DbContext;
            if (efDbContext == null)
            {
                throw new InvalidOperationException("DbContext must inherit from Microsoft.EntityFrameworkCore.DbContext");
            }

            await efDbContext.BulkInsertAsync(
                entityArray,
                new BulkConfig
                {
                    SetOutputIdentity = true,      // 设置是否返回自增ID
                    BatchSize = 1000,              // 批次大小
                    PreserveInsertOrder = true,    // 保持插入顺序
                    BulkCopyTimeout = 300,         // 超时时间（秒）
                    IncludeGraph = true            // 包含导航属性（子表）
                },
                cancellationToken: cancellationToken
            );

            if (autoSave)
            {
                await dbContext.SaveChangesAsync(cancellationToken);
            }
        }

        /// <summary>
        /// 批量更新实体（高性能，使用 EFCore.BulkExtensions）
        /// </summary>
        public virtual async Task BulkUpdateAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            var entityArray = entities.ToArray();
            if (entityArray.Length == 0) return;

            var dbContext = await GetDbContextAsync();
            cancellationToken = GetCancellationToken(cancellationToken);

            // 使用 EFCore.BulkExtensions 的 BulkUpdateAsync 进行高性能批量更新
            var efDbContext = dbContext as DbContext;
            if (efDbContext == null)
            {
                throw new InvalidOperationException("DbContext must inherit from Microsoft.EntityFrameworkCore.DbContext");
            }

            await efDbContext.BulkUpdateAsync(
                entityArray,
                new BulkConfig
                {
                    BatchSize = 1000,              // 批次大小
                    BulkCopyTimeout = 300,         // 超时时间（秒）
                    IncludeGraph = true            // 包含导航属性（子表）
                },
                cancellationToken: cancellationToken
            );

            if (autoSave)
            {
                await dbContext.SaveChangesAsync(cancellationToken);
            }
        }

        /// <summary>
        /// 批量删除实体（高性能，使用 EFCore.BulkExtensions）
        /// </summary>
        public virtual async Task BulkDeleteAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            var entityArray = entities.ToArray();
            if (entityArray.Length == 0) return;

            var dbContext = await GetDbContextAsync();
            cancellationToken = GetCancellationToken(cancellationToken);

            var efDbContext = dbContext as DbContext;
            if (efDbContext == null)
            {
                throw new InvalidOperationException("DbContext must inherit from Microsoft.EntityFrameworkCore.DbContext");
            }

            await efDbContext.BulkDeleteAsync(
                entityArray,
                new BulkConfig
                {
                    BatchSize = 1000,
                    BulkCopyTimeout = 300
                },
                cancellationToken: cancellationToken
            );

            if (autoSave)
            {
                await dbContext.SaveChangesAsync(cancellationToken);
            }
        }

        public async Task<IQueryable<TChild>> GetChildrenQueryableAsync()
        {
            return ApplyChildDataFilters((await GetChildDbSetAsync()).AsQueryable());
        }

        protected async Task<DbSet<TChild>> GetChildDbSetAsync()
        {
            return (await GetDbContextAsync()).Set<TChild>();
        }

        public async Task<IQueryable<TChild>> ChildrenWithDetailsAsync()
        {
            if (AbpEntityOptions.DefaultWithDetailsFunc == null)
            {
                return await GetChildrenQueryableAsync();
            }

            return AbpChildOptions.DefaultWithDetailsFunc(await GetChildrenQueryableAsync());
        }

        protected virtual TQueryable ApplyChildDataFilters<TQueryable>(TQueryable query)
            where TQueryable : IQueryable<TChild>
        {
            return ApplyDataFilters<TQueryable, TChild>(query);
        }

        public override async Task<TEntity> InsertAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            CheckAndSetId(entity);

            var dbContext = await GetDbContextAsync();
            await (await GetQueryableAsync()).CheckUniquePropertyAsync<TEntity, TKey>(entity);

            var savedEntity = (await dbContext.Set<TEntity>().AddAsync(entity, GetCancellationToken(cancellationToken))).Entity;

            if (autoSave)
            {
                await dbContext.SaveChangesAsync(GetCancellationToken(cancellationToken));
            }

            return savedEntity;
        }

        public override async Task InsertManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            var entityArray = entities.ToArray();
            var dbContext = await GetDbContextAsync();
            cancellationToken = GetCancellationToken(cancellationToken);

            foreach (var entity in entityArray)
            {
                CheckAndSetId(entity);
                await (await GetQueryableAsync()).CheckUniquePropertyAsync<TEntity, TKey>(entity);
            }

            if (BulkOperationProvider != null)
            {
                await BulkOperationProvider.InsertManyAsync<TDbContext, TEntity>(
                    this,
                    entityArray,
                    autoSave,
                    GetCancellationToken(cancellationToken)
                );
                return;
            }

            await dbContext.Set<TEntity>().AddRangeAsync(entityArray, cancellationToken);

            if (autoSave)
            {
                await dbContext.SaveChangesAsync(cancellationToken);
            }
        }


        public override async Task<TEntity> UpdateAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            var dbContext = await GetDbContextAsync();
            await (await GetQueryableAsync()).CheckUniquePropertyAsync<TEntity, TKey>(entity);

            dbContext.Attach(entity);

            var updatedEntity = dbContext.Update(entity).Entity;

            if (autoSave)
            {
                await dbContext.SaveChangesAsync(GetCancellationToken(cancellationToken));
            }

            return updatedEntity;
        }

        public override async Task UpdateManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            cancellationToken = GetCancellationToken(cancellationToken);

            if (BulkOperationProvider != null)
            {
                await BulkOperationProvider.UpdateManyAsync<TDbContext, TEntity>(
                    this,
                    entities,
                    autoSave,
                    GetCancellationToken(cancellationToken)
                    );

                return;
            }

            var dbContext = await GetDbContextAsync();

            foreach (var entity in entities)
            {
                await (await GetQueryableAsync()).CheckUniquePropertyAsync<TEntity, TKey>(entity);
            }

            dbContext.Set<TEntity>().UpdateRange(entities);

            if (autoSave)
            {
                await dbContext.SaveChangesAsync(cancellationToken);
            }
        }

        public async Task<long> GetCountAsync(bool includeDetails = false, CancellationToken cancellationToken = default)
        {
            var queryable = includeDetails
                ? await WithDetailsAsync()
                : await GetQueryableAsync();

            return includeDetails
                ? await queryable.LongCountAsync(GetCancellationToken(cancellationToken))
                : await queryable.LongCountAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<long> GetCountAsync(string filter, bool includeDetails = false, CancellationToken cancellationToken = default)
        {
            var queryable = includeDetails
                ? await WithDetailsAsync()
                : await GetQueryableAsync();

            return includeDetails
                ? await queryable.WhereIf(filter != null, x => x.Code.StartsWith(filter)).LongCountAsync(GetCancellationToken(cancellationToken))
                : await queryable.WhereIf(filter != null, x => x.Code.StartsWith(filter)).LongCountAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<long> GetCountAsync(Expression<Func<TEntity, bool>> predicate, bool includeDetails = false, CancellationToken cancellationToken = default)
        {
            var queryable = includeDetails
                ? await WithDetailsAsync()
                : await GetQueryableAsync();

            return includeDetails
                ? await queryable.WhereIf(predicate != null, predicate).LongCountAsync(GetCancellationToken(cancellationToken))
                : await queryable.WhereIf(predicate != null, predicate).LongCountAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<List<TEntity>> GetPagedListAsync(string filter, int skipCount = int.MaxValue, int maxResultCount = 0, string sorting = null, bool includeDetails = false, CancellationToken cancellationToken = default)
        {
            var queryable = includeDetails
                ? await WithDetailsAsync()
                : await GetQueryableAsync();

            return await queryable
                .WhereIf(filter != null, x => x.Code.StartsWith(filter))
                .OrderByIf<TEntity, IQueryable<TEntity>>(!sorting.IsNullOrWhiteSpace(), sorting)
                .PageBy(skipCount, maxResultCount)
                .ToListAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<List<TEntity>> GetPagedListAsync(Expression<Func<TEntity, bool>> predicate, int skipCount = int.MaxValue, int maxResultCount = 0, string sorting = null, bool includeDetails = false, CancellationToken cancellationToken = default)
        {
            var queryable = includeDetails
                ? await WithDetailsAsync()
                : await GetQueryableAsync();

            return await queryable
                .WhereIf(predicate != null, predicate)
                .OrderByIf<TEntity, IQueryable<TEntity>>(!sorting.IsNullOrWhiteSpace(), sorting)
                .PageBy(skipCount, maxResultCount)
                .ToListAsync(GetCancellationToken(cancellationToken));
        }

        public virtual async Task<TChild> GetChildAsync(TKey id, bool includeDetails = true, CancellationToken cancellationToken = default)
        {
            var entity = await FindChildAsync(id, includeDetails, GetCancellationToken(cancellationToken));

            if (entity == null)
            {
                throw new EntityNotFoundException(typeof(TChild), id);
            }

            return entity;
        }

        public virtual async Task<TChild> FindChildAsync(TKey id, bool includeDetails = true, CancellationToken cancellationToken = default)
        {
            return includeDetails
                ? await (await ChildrenWithDetailsAsync()).OrderBy(e => e.Id).FirstOrDefaultAsync(e => e.Id.Equals(id), GetCancellationToken(cancellationToken))
                : await (await GetChildDbSetAsync()).FindAsync(new object[] { id }, GetCancellationToken(cancellationToken));
        }

        public async Task<List<TChild>> GetChildrenPagedListAsync(Expression<Func<TChild, bool>> predicate, int skipCount = int.MaxValue, int maxResultCount = 0, string sorting = null, bool includeDetails = false, CancellationToken cancellationToken = default)
        {
            var queryable = includeDetails
                ? await ChildrenWithDetailsAsync()
                : await GetChildrenQueryableAsync();

            return await queryable
                .WhereIf(predicate != null, predicate)
                .OrderByIf<TChild, IQueryable<TChild>>(!sorting.IsNullOrWhiteSpace(), sorting)
                .PageBy(skipCount, maxResultCount)
                .ToListAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<long> GetChildrenCountAsync(bool includeDetails = false, CancellationToken cancellationToken = default)
        {
            var queryable = includeDetails
                ? await ChildrenWithDetailsAsync()
                : await GetChildrenQueryableAsync();

            return includeDetails
                ? await queryable.LongCountAsync(GetCancellationToken(cancellationToken))
                : await queryable.LongCountAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<long> GetChildrenCountAsync(Expression<Func<TChild, bool>> predicate, bool includeDetails = false, CancellationToken cancellationToken = default)
        {
            var queryable = includeDetails
                ? await ChildrenWithDetailsAsync()
                : await GetChildrenQueryableAsync();

            return includeDetails
                ? await queryable.WhereIf(predicate != null, predicate).LongCountAsync(GetCancellationToken(cancellationToken))
                : await queryable.WhereIf(predicate != null, predicate).LongCountAsync(GetCancellationToken(cancellationToken));
        }
    }

    public class MultiArchiveRepository<TDbContext, TEntity, TChild> : MultiArchiveRepository<TDbContext, TEntity, TChild, Guid>,
        IMultiArchiveRepository<TEntity, TChild>
        where TDbContext : IEfCoreDbContext
        where TEntity : class, IMultiArchiveAggregateRoot<TChild>
        where TChild : class, IArchiveChildAggregateRoot
    {
        public MultiArchiveRepository(IDbContextProvider<TDbContext> dbContextProvider) : base(dbContextProvider)
        {
        }
    }
}
